/*
 * Copyright (C) 2006 The Android Open Source Project
 * Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package tv.danmaku.ijk.media.player;

import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaDataSource;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.view.Surface;
import android.view.SurfaceHolder;

import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Map;

import tv.danmaku.ijk.media.player.misc.AndroidTrackInfo;
import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
import tv.danmaku.ijk.media.player.misc.ITrackInfo;
import tv.danmaku.ijk.media.player.pragma.DebugLog;

public class AndroidMediaPlayer extends AbstractMediaPlayer {
	private final MediaPlayer mInternalMediaPlayer;
	private final AndroidMediaPlayerListenerHolder mInternalListenerAdapter;
	private String mDataSource;
	private MediaDataSource mMediaDataSource;

	private final Object mInitLock = new Object();
	private boolean mIsReleased;

	private static MediaInfo sMediaInfo;

	public AndroidMediaPlayer() {
		synchronized (mInitLock) {
			mInternalMediaPlayer = new MediaPlayer();
		}
		mInternalMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
		mInternalListenerAdapter = new AndroidMediaPlayerListenerHolder(this);
		attachInternalListeners();
	}

	public MediaPlayer getInternalMediaPlayer() {
		return mInternalMediaPlayer;
	}

	@Override
	public void setDisplay(SurfaceHolder sh) {
		synchronized (mInitLock) {
			if (!mIsReleased) {
				mInternalMediaPlayer.setDisplay(sh);
			}
		}
	}

	@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
	@Override
	public void setSurface(Surface surface) {
		mInternalMediaPlayer.setSurface(surface);
	}

	@Override
	public void setDataSource(Context context, Uri uri) throws IOException,
			IllegalArgumentException, SecurityException, IllegalStateException {
		mInternalMediaPlayer.setDataSource(context, uri);
	}

	@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
	@Override
	public void setDataSource(Context context, Uri uri,
			Map<String, String> headers) throws IOException,
			IllegalArgumentException, SecurityException, IllegalStateException {
		mInternalMediaPlayer.setDataSource(context, uri, headers);
	}

	@Override
	public void setDataSource(FileDescriptor fd) throws IOException,
			IllegalArgumentException, IllegalStateException {
		mInternalMediaPlayer.setDataSource(fd);
	}

	@Override
	public void setDataSource(String path) throws IOException,
			IllegalArgumentException, SecurityException, IllegalStateException {
		mDataSource = path;

		Uri uri = Uri.parse(path);
		String scheme = uri.getScheme();
		if (!TextUtils.isEmpty(scheme) && scheme.equalsIgnoreCase("file")) {
			mInternalMediaPlayer.setDataSource(uri.getPath());
		} else {
			mInternalMediaPlayer.setDataSource(path);
		}
	}

	@TargetApi(Build.VERSION_CODES.M)
	@Override
	public void setDataSource(IMediaDataSource mediaDataSource) {
		releaseMediaDataSource();

		mMediaDataSource = new MediaDataSourceProxy(mediaDataSource);
		mInternalMediaPlayer.setDataSource(mMediaDataSource);
	}

	@TargetApi(Build.VERSION_CODES.M)
	private static class MediaDataSourceProxy extends MediaDataSource {
		private final IMediaDataSource mMediaDataSource;

		public MediaDataSourceProxy(IMediaDataSource mediaDataSource) {
			mMediaDataSource = mediaDataSource;
		}

		@Override
		public int readAt(long position, byte[] buffer, int offset, int size)
				throws IOException {
			return mMediaDataSource.readAt(position, buffer, offset, size);
		}

		@Override
		public long getSize() throws IOException {
			return mMediaDataSource.getSize();
		}

		@Override
		public void close() throws IOException {
			mMediaDataSource.close();
		}
	}

	@Override
	public String getDataSource() {
		return mDataSource;
	}

	private void releaseMediaDataSource() {
		if (mMediaDataSource != null) {
			try {
				mMediaDataSource.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			mMediaDataSource = null;
		}
	}

	@Override
	public void prepareAsync() throws IllegalStateException {
		mInternalMediaPlayer.prepareAsync();
	}

	@Override
	public void start() throws IllegalStateException {
		mInternalMediaPlayer.start();
	}

	@Override
	public void stop() throws IllegalStateException {
		mInternalMediaPlayer.stop();
	}

	@Override
	public void pause() throws IllegalStateException {
		mInternalMediaPlayer.pause();
	}

	@Override
	public void setScreenOnWhilePlaying(boolean screenOn) {
		mInternalMediaPlayer.setScreenOnWhilePlaying(screenOn);
	}

	@Override
	public ITrackInfo[] getTrackInfo() {
		return AndroidTrackInfo.fromMediaPlayer(mInternalMediaPlayer);
	}

	@Override
	public int getVideoWidth() {
		return mInternalMediaPlayer.getVideoWidth();
	}

	@Override
	public int getVideoHeight() {
		return mInternalMediaPlayer.getVideoHeight();
	}

	@Override
	public int getVideoSarNum() {
		return 1;
	}

	@Override
	public int getVideoSarDen() {
		return 1;
	}

	@Override
	public boolean isPlaying() {
		try {
			return mInternalMediaPlayer.isPlaying();
		} catch (IllegalStateException e) {
			DebugLog.printStackTrace(e);
			return false;
		}
	}

	@Override
	public void seekTo(long msec) throws IllegalStateException {
		mInternalMediaPlayer.seekTo((int) msec);
	}

	@Override
	public long getCurrentPosition() {
		try {
			return mInternalMediaPlayer.getCurrentPosition();
		} catch (IllegalStateException e) {
			DebugLog.printStackTrace(e);
			return 0;
		}
	}

	@Override
	public long getDuration() {
		try {
			return mInternalMediaPlayer.getDuration();
		} catch (IllegalStateException e) {
			DebugLog.printStackTrace(e);
			return 0;
		}
	}

	@Override
	public void release() {
		mIsReleased = true;
		mInternalMediaPlayer.release();
		releaseMediaDataSource();
		resetListeners();
		attachInternalListeners();
	}

	@Override
	public void reset() {
		try {
			mInternalMediaPlayer.reset();
		} catch (IllegalStateException e) {
			DebugLog.printStackTrace(e);
		}
		releaseMediaDataSource();
		resetListeners();
		attachInternalListeners();
	}

	@Override
	public void setLooping(boolean looping) {
		mInternalMediaPlayer.setLooping(looping);
	}

	@Override
	public boolean isLooping() {
		return mInternalMediaPlayer.isLooping();
	}

	@Override
	public void setVolume(float leftVolume, float rightVolume) {
		mInternalMediaPlayer.setVolume(leftVolume, rightVolume);
	}

	@Override
	public int getAudioSessionId() {
		return mInternalMediaPlayer.getAudioSessionId();
	}

	@Override
	public MediaInfo getMediaInfo() {
		if (sMediaInfo == null) {
			MediaInfo module = new MediaInfo();

			module.mVideoDecoder = "android";
			module.mVideoDecoderImpl = "HW";

			module.mAudioDecoder = "android";
			module.mAudioDecoderImpl = "HW";

			sMediaInfo = module;
		}

		return sMediaInfo;
	}

	@Override
	public void setLogEnabled(boolean enable) {
	}

	@Override
	public boolean isPlayable() {
		return true;
	}

	/*--------------------
	 * misc
	 */
	@Override
	public void setWakeMode(Context context, int mode) {
		mInternalMediaPlayer.setWakeMode(context, mode);
	}

	@Override
	public void setAudioStreamType(int streamtype) {
		mInternalMediaPlayer.setAudioStreamType(streamtype);
	}

	@Override
	public void setKeepInBackground(boolean keepInBackground) {
	}

	/*--------------------
	 * Listeners adapter
	 */
	private void attachInternalListeners() {
		mInternalMediaPlayer.setOnPreparedListener(mInternalListenerAdapter);
		mInternalMediaPlayer
				.setOnBufferingUpdateListener(mInternalListenerAdapter);
		mInternalMediaPlayer.setOnCompletionListener(mInternalListenerAdapter);
		mInternalMediaPlayer
				.setOnSeekCompleteListener(mInternalListenerAdapter);
		mInternalMediaPlayer
				.setOnVideoSizeChangedListener(mInternalListenerAdapter);
		mInternalMediaPlayer.setOnErrorListener(mInternalListenerAdapter);
		mInternalMediaPlayer.setOnInfoListener(mInternalListenerAdapter);
	}

	private class AndroidMediaPlayerListenerHolder implements
			MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener,
			MediaPlayer.OnBufferingUpdateListener,
			MediaPlayer.OnSeekCompleteListener,
			MediaPlayer.OnVideoSizeChangedListener,
			MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener {
		public final WeakReference<AndroidMediaPlayer> mWeakMediaPlayer;

		public AndroidMediaPlayerListenerHolder(AndroidMediaPlayer mp) {
			mWeakMediaPlayer = new WeakReference<AndroidMediaPlayer>(mp);
		}

		@Override
		public boolean onInfo(MediaPlayer mp, int what, int extra) {
			AndroidMediaPlayer self = mWeakMediaPlayer.get();
			return self != null && notifyOnInfo(what, extra);

		}

		@Override
		public boolean onError(MediaPlayer mp, int what, int extra) {
			AndroidMediaPlayer self = mWeakMediaPlayer.get();
			return self != null && notifyOnError(what, extra);

		}

		@Override
		public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
			AndroidMediaPlayer self = mWeakMediaPlayer.get();
			if (self == null)
				return;

			notifyOnVideoSizeChanged(width, height, 1, 1);
		}

		@Override
		public void onSeekComplete(MediaPlayer mp) {
			AndroidMediaPlayer self = mWeakMediaPlayer.get();
			if (self == null)
				return;

			notifyOnSeekComplete();
		}

		@Override
		public void onBufferingUpdate(MediaPlayer mp, int percent) {
			AndroidMediaPlayer self = mWeakMediaPlayer.get();
			if (self == null)
				return;

			notifyOnBufferingUpdate(percent);
		}

		@Override
		public void onCompletion(MediaPlayer mp) {
			AndroidMediaPlayer self = mWeakMediaPlayer.get();
			if (self == null)
				return;

			notifyOnCompletion();
		}

		@Override
		public void onPrepared(MediaPlayer mp) {
			AndroidMediaPlayer self = mWeakMediaPlayer.get();
			if (self == null)
				return;

			notifyOnPrepared();
		}
	}
}
